//  
//  File Name   :	Matrix4.cpp
//  Description :	Matrix4 implementation file.
//  Author      :	William McVicar
//  Mail        :	mcvicar.william@gmail.com
//  
//  Design Patterns :
//  

//  Precompiled Header
#include "CommonDefines.h"

//  Library Includes

//  Local Includes
#include "MathCommon.h"

//  This Includes
#include "Matrix4.h"


//  Static Variables

namespace Protein
{

const Matrix4 Matrix4::Identity = Matrix4(1,0,0,0,
											                    0,1,0,0,
											                    0,0,1,0,
											                    0,0,0,1);

const Matrix4 Matrix4::Zero = Matrix4(0,0,0,0,
											                0,0,0,0,
											                0,0,0,0,
											                0,0,0,0);


//  Static Function Prototypes

//  Implementation

Matrix4::Matrix4()
{
	memset( m_data, 0, sizeof( float ) * 16 );

	m_data[0]		= 1.0f;
	m_data[5]		= 1.0f;
	m_data[10]	= 1.0f;
	m_data[15]	= 1.0f;
}

Matrix4::Matrix4(	float _11, float _12, float _13, float _14,
					        float _21, float _22, float _23, float _24,
					        float _31, float _32, float _33, float _34,
					        float _41, float _42, float _43, float _44 )
{
	m_data[0]  = _11;
	m_data[1]  = _12;
	m_data[2]  = _13;
	m_data[3]  = _14;
	m_data[4]  = _21;
	m_data[5]  = _22;
	m_data[6]  = _23;
	m_data[7]  = _24;
	m_data[8]  = _31;
	m_data[9]  = _32;
	m_data[10] = _33;
	m_data[11] = _34;
	m_data[12] = _41;
	m_data[13] = _42;
	m_data[14] = _43;
	m_data[15] = _44;

}

Matrix4::Matrix4( float* values )
{
  memcpy( m_data, values, sizeof( float ) * 16 );
}

Matrix4::Matrix4( const Matrix4& other )
{
  memcpy( m_data, other.m_data, sizeof( float ) * 16 );
}

Matrix4& Matrix4::operator=( const Matrix4& rhs )
{
	memcpy( m_data, rhs.m_data, sizeof( float ) * 16 );
	return *this;
}

Matrix4 Matrix4::XRot(float theta)
{
	Matrix4 mResult;

	float fSinTheta		 = sinf( theta );
	float fCosTheta		 = cosf( theta );
	float fNegSinTheta = Mathf::FlipSign( fSinTheta );

  mResult.m_data[5]  = fCosTheta;
  mResult.m_data[6]  = fNegSinTheta;
  mResult.m_data[9]  = fSinTheta;
  mResult.m_data[10] = fCosTheta;

	return mResult;
}

Matrix4 Matrix4::YRot(float theta)
{
	Matrix4 mResult;

	float fSinTheta		= Mathf::Sin( theta );
	float fCosTheta		= Mathf::Cos( theta );
	float fNegSinTheta = Mathf::FlipSign( fSinTheta );

	memset( &mResult.m_data, 0, sizeof( float ) * 16 );

	mResult.m_data[0]  = fCosTheta;
  mResult.m_data[2]  = fSinTheta;
  mResult.m_data[5]  = 1.0f;
  mResult.m_data[8]  = fNegSinTheta;
  mResult.m_data[10] = fCosTheta;
  mResult.m_data[15] = 1.0f;

	return mResult;
}

Matrix4 Matrix4::ZRot(float theta)
{
	Matrix4 mResult;

	float fSinTheta		   = sinf( theta );
	float fCosTheta		   = cosf( theta );
	float fNegSinTheta   = Mathf::FlipSign( fSinTheta ); 

	mResult.m_data[0]  = fCosTheta;
  mResult.m_data[1]  = fNegSinTheta;
  mResult.m_data[4]  = fSinTheta;
  mResult.m_data[5]  = fCosTheta;

	return mResult;
}

void Matrix4::makeXRot(float theta)
{
	float fSinTheta		  = Mathf::Sin( theta );
	float fCosTheta		  = Mathf::Cos( theta );
  float fNegSinTheta  = Mathf::FlipSign( fSinTheta ); 

	m_data[0]  = 1.0f;
  m_data[1]  = 0.0f;
  m_data[2]  = 0.0f;
  m_data[3]  = 0.0f;
  m_data[4]  = 0.0f;
  m_data[5]  = fCosTheta;
  m_data[6]  = fNegSinTheta;
  m_data[7]  = 0.0f;
  m_data[8]  = 0.0f;
  m_data[9]  = fSinTheta;
  m_data[10] = fCosTheta;
  m_data[11] = 0.0f;
  m_data[12] = 0.0f;
  m_data[13] = 0.0f;
  m_data[14] = 0.0f;
  m_data[15] = 1.0f;
}

void Matrix4::makeYRot(float theta)
{
	float fSinTheta		= sinf( theta );
	float fCosTheta		= cosf( theta );
	float fNegSinTheta = Mathf::FlipSign( fSinTheta );

	memset( &m_data, 0, sizeof( float ) * 16 );

	m_data[0]  = fCosTheta;
  m_data[1]  = 0.0f;
  m_data[2]  = fSinTheta;
  m_data[3]  = 0.0f;
  m_data[4]  = 0.0f;
  m_data[5]  = 1.0f;
  m_data[6]  = 0.0f;
  m_data[7]  = 0.0f;
  m_data[8]  = fNegSinTheta;
  m_data[9]  = 0.0f;
  m_data[10] = fCosTheta;
  m_data[11] = 0.0f;
  m_data[12] = 0.0f;
  m_data[13] = 0.0f;
  m_data[14] = 0.0f;
  m_data[15] = 1.0f;
}

void Matrix4::makeZRot(float theta)
{
	float fSinTheta		 = sinf( theta );
	float fCosTheta		 = cosf( theta ); 
	float fNegSinTheta = Mathf::FlipSign( fSinTheta );

	memset( &m_data, 0, sizeof( float ) * 16 );

	m_data[0] = fCosTheta;
  m_data[1] = fNegSinTheta;
  m_data[2]  = 0.0f;
  m_data[3]  = 0.0f;
  m_data[4] = fSinTheta;
  m_data[5] = fCosTheta;
  m_data[6]  = 0.0f;
  m_data[7]  = 0.0f;
  m_data[8]  = 0.0f;
  m_data[9]  = 0.0f;
  m_data[10] = 0.0f;
  m_data[11] = 0.0f;
  m_data[12] = 0.0f;
  m_data[13] = 0.0f;
  m_data[14] = 0.0f;
  m_data[15] = 1.0f;
}

void Matrix4::makeTranslation(const Vector3f& translation )
{
  m_data[0]  = 1.0f; m_data[1]  = 0.0f; m_data[2]   = 0.0f; m_data[3]  = translation.x;
  m_data[4]  = 0.0f; m_data[5]  = 1.0f; m_data[6]   = 0.0f; m_data[7]  = translation.y;
  m_data[8]  = 0.0f; m_data[9]  = 0.0f; m_data[10]  = 1.0f; m_data[11] = translation.z;
  m_data[12] = 0.0f; m_data[13] = 0.0f; m_data[14]  = 0.0f; m_data[15] = 1.0f;
}

void Matrix4::makeScale(const Vector3f& scale)
{
  m_data[0]  = scale.x; m_data[1]  = 0.0f;    m_data[2]   = 0.0f;     m_data[3]  = 0.0f;
  m_data[4]  = 0.0f;    m_data[5]  = scale.y; m_data[6]   = 0.0f;     m_data[7]  = 0.0f;
  m_data[8]  = 0.0f;    m_data[9]  = 0.0f;    m_data[10]  = scale.z;  m_data[11] = 0.0f;
  m_data[12] = 0.0f;    m_data[13] = 0.0f;    m_data[14]  = 0.0f;     m_data[15] = 1.0f;
}

void Matrix4::makeAxisAngle(const Vector3f& inAxis, float angle)
{
	float fCos	 = cosf( angle );
	float fSin	 = sinf( angle );
	float t		   = 1.0f - fCos;

	float fSX	   = fSin * inAxis.x;
	float fSY	   = fSin * inAxis.y;
	float fSZ	   = fSin * inAxis.z;

	float fXY	   = inAxis.x * inAxis.y;
	float fXZ	   = inAxis.x * inAxis.z;
	float fYZ	   = inAxis.y * inAxis.z;

	m_data[0]  = t * inAxis.x * inAxis.x + fCos;
	m_data[5]  = t * inAxis.y * inAxis.y + fCos;
	m_data[10] = t * inAxis.z * inAxis.z + fCos;

	m_data[1]  = t * fXY - fSZ;
	m_data[2]  = t * fXZ + fSY;
	m_data[4]  = t * fXY + fSZ;
	m_data[6]  = t * fYZ - fSX;
	m_data[8]  = t * fXZ - fSY;
	m_data[9]  = t * fYZ + fSX;

	m_data[3]  = 0.0f;
	m_data[7]  = 0.0f;
	m_data[11] = 0.0f;
	m_data[12] = 0.0f;
	m_data[13] = 0.0f;
	m_data[14] = 0.0f;
	m_data[15] = 1.0f;
}

#define PROTEIN_SWAP( A, B )\
{\
  float temp = A;\
  A = B;\
  B = temp;\
}

void Matrix4::makeTranspose()
{
  PROTEIN_SWAP( m_data[1],  m_data[4]  );
  PROTEIN_SWAP( m_data[2],  m_data[8]  );
  PROTEIN_SWAP( m_data[3],  m_data[12] );
  PROTEIN_SWAP( m_data[7],  m_data[13] );
  PROTEIN_SWAP( m_data[9],  m_data[6]  );
  PROTEIN_SWAP( m_data[11], m_data[14] );
  PROTEIN_SWAP( m_data[12], m_data[3]  );
}

void
Matrix4::makeCameraLookAt( const Vector3f& _krLoc, 
						               const Vector3f& _krLookAt, 
						               const Vector3f& _krInUp )
{
  Vector3f ZAxis = ( _krLookAt - _krLoc ).normalised();
	Vector3f XAxis = Vector3f::cross( _krInUp, ZAxis ).normalised();
	Vector3f YAxis = Vector3f::cross( ZAxis, XAxis );

	m_data[0]  = XAxis.x;
	m_data[1]  = YAxis.x;
	m_data[2]  = ZAxis.x;
	m_data[3]  = 0;

	m_data[4]  = XAxis.y;
	m_data[5]  = YAxis.y;
	m_data[6]  = ZAxis.y;
	m_data[7]  = 0;

	m_data[8]  = XAxis.z;
	m_data[9]  = YAxis.z;
	m_data[10] = ZAxis.z;
	m_data[11] = 0;

	m_data[12] = Mathf::FlipSign( Vector3f::dot( XAxis, _krLoc ) );
	m_data[13] = Mathf::FlipSign( Vector3f::dot( YAxis, _krLoc ) );
	m_data[14] = Mathf::FlipSign( Vector3f::dot( ZAxis, _krLoc ) );
	m_data[15] = 1.0f;
}

void 
Matrix4::makeCameraOrthographicProjection( float _fWidth,
										                       float _fHeight,
									                         float _fNear,
									                         float _fFar )
{

	memset( m_data, 0, sizeof( float ) * 16 );

	m_data[0]  = 2.0f   / _fWidth;
	m_data[5]  = 2.0f   / _fHeight;
	m_data[10] = 1.0f   / ( _fFar  - _fNear );
	m_data[14] = _fNear / ( _fNear - _fFar );
	m_data[15] = 1.0f;
}

void 
Matrix4::makeCameraPerspectiveProjection(float _kfDMin,
										                     float _kfDMax,
										                     float _kfRMax,
										                     float _kfRMin,
										                     float _kfUMax,
										                     float _kfUMin )
{
	memset( m_data, 0, sizeof( float ) * 16 );

	m_data[0]  = ( 2 * _kfDMin ) / ( _kfUMax - _kfUMin );
	m_data[5]  = ( 2 * _kfDMin ) / ( _kfRMax - _kfRMin );
	m_data[10] = _kfDMax / ( _kfDMax - _kfDMin );
	m_data[11] = 1.0f;
	m_data[14] = ( -1.0f * _kfDMin * _kfDMax ) / ( _kfDMax - _kfDMin );
}

void Matrix4::makeIdentity()
{
  memcpy( m_data, Matrix4::Identity.m_data, sizeof( float ) * 16 );
}

Matrix4 Matrix4::transpose()
{
  return Matrix4( m_data[0], m_data[4], m_data[8],  m_data[12],
						      m_data[1], m_data[5], m_data[9],  m_data[13], 
						      m_data[2], m_data[6], m_data[10], m_data[14], 
						      m_data[3], m_data[7], m_data[11], m_data[15] );
}

void Matrix4::rotate(float _fYaw, float _fPitch, float _fRoll)
{
	float CX = cosf( _fRoll  );		float SX = sinf( _fRoll	 );
	float CY = cosf( _fPitch );		float SY = sinf( _fPitch );
	float CZ = cosf( _fYaw   );		float SZ = sinf( _fYaw	 );

	m_data[0]  = CY * CZ;
	m_data[1]  = Mathf::FlipSign( CY ) * SZ;
	m_data[2]  = SY;
	m_data[3]  = 0.0f;

	m_data[4]  = ( SX * SY * CZ ) + ( CX * SZ );
	m_data[5]  = ( Mathf::FlipSign( SX ) * SY * SZ ) +( CX * CZ );
	m_data[6]  = ( Mathf::FlipSign( SX ) * CY );
	m_data[7]  = 0.0f;

	m_data[8]  = ( Mathf::FlipSign( CX ) * SY * CZ ) +( SX * SZ );
	m_data[9]  = ( CX * SY * SZ ) +( SX * CZ );
	m_data[10] = ( CX * CY );
	m_data[11] = 0.0f;

	m_data[12] = 0.0f;
	m_data[13] = 0.0f;
	m_data[14] = 0.0f;
	m_data[15] = 1.0f;
}

void Matrix4::translate(const Vector3f& v)
{
	m_data[03] += v.x;
	m_data[07] += v.y;
	m_data[11] += v.z; 
}

void Matrix4::scale(const Vector3f& scale)
{
	m_data[0] *= scale.x;
	m_data[1] *= scale.y;
	m_data[2] *= scale.z;
}

Matrix4 Matrix4::AxisAngle(const Vector3f& inAxis, float angle)
{
	Matrix4 mResult;

	float fCos	         = cosf( angle );
	float fSin	         = sinf( angle );
	float t		           = 1.0f - fCos;

	float fSX	           = fSin * inAxis.x;
	float fSY	           = fSin * inAxis.y;
	float fSZ	           = fSin * inAxis.z;

	float fXY	           = inAxis.x * inAxis.y;
	float fXZ	           = inAxis.x * inAxis.z;
	float fYZ	           = inAxis.y * inAxis.z;

	mResult.m_data[0]  = t * inAxis.x * inAxis.x + fCos;
	mResult.m_data[5]  = t * inAxis.y * inAxis.y + fCos;
	mResult.m_data[10] = t * inAxis.z * inAxis.z + fCos;

	mResult.m_data[1]  = t * fXY - fSZ;
	mResult.m_data[2]  = t * fXZ + fSY;
	mResult.m_data[4]  = t * fXY + fSZ;
	mResult.m_data[6]  = t * fYZ - fSX;
	mResult.m_data[8]  = t * fXZ - fSY;
	mResult.m_data[9]  = t * fYZ + fSX;

	mResult.m_data[3]  = 0.0f;
	mResult.m_data[7]  = 0.0f;
	mResult.m_data[11] = 0.0f;
	mResult.m_data[12] = 0.0f;
	mResult.m_data[13] = 0.0f;
	mResult.m_data[14] = 0.0f;
	mResult.m_data[15] = 1.0f;

	return mResult;
}

Matrix4
Matrix4::Translation(const Vector3f& _krP)
{
	return Matrix4(	1.0f, 0.0f, 0.0f, _krP.x,
						      0.0f, 1.0f, 0.0f, _krP.y,
						      0.0f, 0.0f, 1.0f, _krP.z,
						      0.0f, 0.0f, 0.0f, 1.0f );	
}

Vector3f
Matrix4::Transform( const Vector3f& _krIn )
{
	return Vector3f( *this * _krIn );
}

Vector4f 
Matrix4::Transform(const Vector4f& _krIn)
{
	return Vector4f( *this * _krIn );
}

Matrix4
Matrix4::CreateScale(const Vector3f& scale)
{
	return Matrix4( scale.x, 0.0f,			  0.0f,				0.0f,
					        0.0f,			  scale.y, 0.0f,				0.0f,
					        0.0f,			  0.0f,			  scale.z,	0.0f,
					        0.0f,			  0.0f,			  0.0f,				1.0f );
}

Matrix4 
Matrix4::CreateUniformScale( const float _krValue )
{
	return Matrix4( _krValue, 0.0f,	    0.0f,	    0.0f,
					        0.0f,	    _krValue, 0.0f,	    0.0f,
					        0.0f,	    0.0f,	    _krValue, 0.0f,
					        0.0f,	    0.0f,	    0.0f,	    1.0f );
}

void
Matrix4::ToInverse( const Matrix4 &_krIn )
{
   // Find CoFactors
   float cofactorA = _krIn.m_data[5] * _krIn.m_data[10] - _krIn.m_data[6] * _krIn.m_data[9];
   float cofactorB = _krIn.m_data[6] * _krIn.m_data[8]  - _krIn.m_data[4] * _krIn.m_data[10];
   float cofactorC = _krIn.m_data[4] * _krIn.m_data[9]  - _krIn.m_data[5] * _krIn.m_data[8];

	//compute Determinant
	float fDet = ( _krIn.m_data[0] * cofactorA ) + ( _krIn.m_data[1] * cofactorB ) + ( _krIn.m_data[2] * cofactorC );

	if( fDet < Mathf::EPSILON && fDet > -Mathf::EPSILON )
  {
      assert( false && "Determinant is 0" );
  }

  // create adjunct matrix and multiply by 1/det to get upper 3x3
  float fInvDet = 1.0f/fDet;

	//temp float for the transpose

	m_data[0]  = fInvDet * cofactorA;
  m_data[4]  = fInvDet * cofactorB;
  m_data[8]  = fInvDet * cofactorC;
   
  m_data[1]  = fInvDet *( _krIn.m_data[2]*_krIn.m_data[9]  - _krIn.m_data[1] * _krIn.m_data[10] );
  m_data[5]  = fInvDet *( _krIn.m_data[0]*_krIn.m_data[10] - _krIn.m_data[2] * _krIn.m_data[8]  );
  m_data[9]  = fInvDet *( _krIn.m_data[1]*_krIn.m_data[8]  - _krIn.m_data[0] * _krIn.m_data[9]  );

  m_data[2]  = fInvDet *( _krIn.m_data[1]*_krIn.m_data[6] - _krIn.m_data[2]*_krIn.m_data[5]);
  m_data[6]  = fInvDet *( _krIn.m_data[2]*_krIn.m_data[4] - _krIn.m_data[0]*_krIn.m_data[6]);
  m_data[10] = fInvDet *( _krIn.m_data[0]*_krIn.m_data[5] - _krIn.m_data[1]*_krIn.m_data[4]);

    // multiply -translation by inverted 3x3 to get its inverse
	m_data[3]  = m_data[0] * -_krIn.m_data[3] + m_data[1] * (-_krIn.m_data[7]) + m_data[2]  * (-_krIn.m_data[11]);
                                                                                                                            
  m_data[7]  = m_data[4] * -_krIn.m_data[3] + m_data[5] * (-_krIn.m_data[7]) + m_data[6]  * (-_krIn.m_data[11]);
                                                                                                                            
  m_data[11] = m_data[8] * -_krIn.m_data[3] + m_data[9] * (-_krIn.m_data[7]) + m_data[10] * (-_krIn.m_data[11]);

	//set other values around edges
	m_data[12] = _krIn.m_data[12];
	m_data[13] = _krIn.m_data[13];
	m_data[14] = _krIn.m_data[14];
	m_data[15] = 1.0f;
}

Matrix4
Matrix4::Inverse(const Matrix4 &_krIn)
{
	Matrix4 mResult;
    
    // Find CoFactors
   float cofactorA = _krIn.m_data[5] * _krIn.m_data[10] - _krIn.m_data[6] * _krIn.m_data[9];
   float cofactorB = _krIn.m_data[6] * _krIn.m_data[8]  - _krIn.m_data[4] * _krIn.m_data[10];
   float cofactorC = _krIn.m_data[4] * _krIn.m_data[9]  - _krIn.m_data[5] * _krIn.m_data[8];

	//compute Determinant
	float fDet =( _krIn.m_data[0] * cofactorA ) + 
				      ( _krIn.m_data[1] * cofactorB ) + 
				      ( _krIn.m_data[2] * cofactorC );

	if( fDet < Mathf::EPSILON && fDet > -Mathf::EPSILON )
  {
      assert( false && "Determinant is 0" );
  }

  // create adjunct matrix and multiply by 1/det to get upper 3x3
  float fInvDet = 1.0f/fDet;

	//temp float for the transpose

	mResult.m_data[0]  = fInvDet * cofactorA;
  mResult.m_data[4]  = fInvDet * cofactorB;
  mResult.m_data[8]  = fInvDet * cofactorC;
   
  mResult.m_data[1]  = fInvDet *( _krIn.m_data[2]*_krIn.m_data[9]  - _krIn.m_data[1] * _krIn.m_data[10] );
  mResult.m_data[5]  = fInvDet *( _krIn.m_data[0]*_krIn.m_data[10] - _krIn.m_data[2] * _krIn.m_data[8]  );
  mResult.m_data[9]  = fInvDet *( _krIn.m_data[1]*_krIn.m_data[8]  - _krIn.m_data[0] * _krIn.m_data[9]  );

  mResult.m_data[2]  = fInvDet *( _krIn.m_data[1]*_krIn.m_data[6] - _krIn.m_data[2]*_krIn.m_data[5]);
  mResult.m_data[6]  = fInvDet *( _krIn.m_data[2]*_krIn.m_data[4] - _krIn.m_data[0]*_krIn.m_data[6]);
  mResult.m_data[10] = fInvDet *( _krIn.m_data[0]*_krIn.m_data[5] - _krIn.m_data[1]*_krIn.m_data[4]);

  // multiply -translation by inverted 3x3 to get its inverse
	mResult.m_data[3]  =	mResult.m_data[0]  * Mathf::FlipSign( _krIn.m_data[3] ) + mResult.m_data[1]  * Mathf::FlipSign( _krIn.m_data[7] ) +  mResult.m_data[2]  * Mathf::FlipSign( _krIn.m_data[11] );

  mResult.m_data[7]  =	mResult.m_data[4]  * Mathf::FlipSign( _krIn.m_data[3] ) + mResult.m_data[5]  * Mathf::FlipSign( _krIn.m_data[7] ) + mResult.m_data[6]  * Mathf::FlipSign( _krIn.m_data[11] );

  mResult.m_data[11] =	mResult.m_data[8]  * Mathf::FlipSign( _krIn.m_data[3] ) + mResult.m_data[9]  * Mathf::FlipSign( _krIn.m_data[7] ) + mResult.m_data[10] * Mathf::FlipSign( _krIn.m_data[11] );

	//set other values around edges
	mResult.m_data[12] = _krIn.m_data[12];
	mResult.m_data[13] = _krIn.m_data[13];
	mResult.m_data[14] = _krIn.m_data[14];
	mResult.m_data[15] = 1.0f;

	return mResult;
}

void Matrix4::Invert()
{
  // Find CoFactors
   float cofactorA = m_data[5] * m_data[10] - m_data[6] * m_data[9];
   float cofactorB = m_data[6] * m_data[8]  - m_data[4] * m_data[10];
   float cofactorC = m_data[4] * m_data[9]  - m_data[5] * m_data[8];

	//compute Determinant
	float fDet = ( m_data[0] * cofactorA ) + ( m_data[1] * cofactorB ) + ( m_data[2] * cofactorC );

	if( fDet < Mathf::EPSILON && fDet > -Mathf::EPSILON )
  {
      assert( false && "Determinant is 0" );
  }

  // create adjunct matrix and multiply by 1/det to get upper 3x3
  float fInvDet = 1.0f/fDet;

	//temp float for the transpose

	m_data[0]  = fInvDet * cofactorA;
  m_data[4]  = fInvDet * cofactorB;
  m_data[8]  = fInvDet * cofactorC;
   
  m_data[1]  = fInvDet *( m_data[2]*m_data[9]  - m_data[1] * m_data[10] );
  m_data[5]  = fInvDet *( m_data[0]*m_data[10] - m_data[2] * m_data[8]  );
  m_data[9]  = fInvDet *( m_data[1]*m_data[8]  - m_data[0] * m_data[9]  );

  m_data[2]  = fInvDet *( m_data[1]*m_data[6] - m_data[2]*m_data[5]);
  m_data[6]  = fInvDet *( m_data[2]*m_data[4] - m_data[0]*m_data[6]);
  m_data[10] = fInvDet *( m_data[0]*m_data[5] - m_data[1]*m_data[4]);

    // multiply -translation by inverted 3x3 to get its inverse
	m_data[3]  = m_data[0] * -m_data[3] + m_data[1] * (-m_data[7]) + m_data[2]  * (-m_data[11]);
                                                                                                          
  m_data[7]  = m_data[4] * -m_data[3] + m_data[5] * (-m_data[7]) + m_data[6]  * (-m_data[11]);
                                                                                                          
  m_data[11] = m_data[8] * -m_data[3] + m_data[9] * (-m_data[7]) + m_data[10] * (-m_data[11]);

	//set other values around edges
	m_data[12] = m_data[12];
	m_data[13] = m_data[13];
	m_data[14] = m_data[14];
	m_data[15] = 1.0f;
}

Vector3f
Matrix4::GetAxis(int _iAxis) const
{
	return Vector3f( m_data[ _iAxis * 4 + 0 ],
					         m_data[ _iAxis * 4 + 1 ],
					         m_data[ _iAxis * 4 + 2 ] );
}


const Vector3f
Matrix4::GetTranslation() const
{
	return Vector3f( m_data[3],
					         m_data[7],
					         m_data[11] );
}

Matrix4
Matrix4::CameraLookAt(const Vector3f& _krLoc, 
						          const Vector3f& _krLookAt, 
						          const Vector3f& _krInUp )
{
	Matrix4 mResult;

  Vector3f ZAxis = ( _krLookAt - _krLoc ).normalised();
	Vector3f XAxis = Vector3f::cross( _krInUp, ZAxis ).normalised();
	Vector3f YAxis = Vector3f::cross( ZAxis, XAxis );

	mResult.m_data[0]  = XAxis.x;
	mResult.m_data[1]  = YAxis.x;
	mResult.m_data[2]  = ZAxis.x;
	mResult.m_data[3]  = 0;

	mResult.m_data[4]  = XAxis.y;
	mResult.m_data[5]  = YAxis.y;
	mResult.m_data[6]  = ZAxis.y;
	mResult.m_data[7]  = 0;

	mResult.m_data[8]  = XAxis.z;
	mResult.m_data[9]  = YAxis.z;
	mResult.m_data[10] = ZAxis.z;
	mResult.m_data[11] = 0;

	mResult.m_data[12] = Mathf::FlipSign( Vector3f::dot( XAxis, _krLoc ) );
	mResult.m_data[13] = Mathf::FlipSign( Vector3f::dot( YAxis, _krLoc ) );
	mResult.m_data[14] = Mathf::FlipSign( Vector3f::dot( ZAxis, _krLoc ) );
	mResult.m_data[15] = 1.0f;

	return mResult;
}

Matrix4 
Matrix4::CameraOrthographicProjection( float _fWidth,
									                     float _fHeight,
									                     float _fNear,
									                     float _fFar )
{
	return Matrix4( 2.0f / _fWidth, 0.0f,				      0.0f,						             0.0f,
					        0.0f,			      2.0f / _fHeight,	0.0f,						             0.0f,
					        0.0f,			      0.0f,				      1.0f   / ( _fFar - _fNear ), 0.0f,
					        0.0f,			      0.0f,				      _fNear / ( _fNear - _fFar ), 1.0f );
}

Matrix4 Matrix4::CameraPerspectiveProjection( f32 _fNear,
									                            f32 _fFar,
									                            f32 right,
									                            f32 left,
									                            f32 top,
									                            f32 bottom )
{
	Matrix4 temp;

	temp.m_data[0]  = ( 2.0f * _fNear ) / ( top - bottom );
	temp.m_data[5]  = ( 2.0f * _fNear ) / ( right - left );
	temp.m_data[10] = _fFar / ( _fFar - _fNear );
	temp.m_data[11] = 1.0f;
	temp.m_data[14] = ( -1.0f * _fNear * _fFar ) / ( _fFar - _fNear );

	return temp;
}

bool
Matrix4::operator ==( const Matrix4& rhs )
{
	return ( memcmp( &m_data[0], &rhs.m_data[0], sizeof( float ) * 16 ) == 0 );
}

bool
Matrix4::operator !=( const Matrix4& rhs )
{
	return ( memcmp( &m_data[0], &rhs.m_data[0], sizeof( float ) * 16 ) != 0 );
}

Matrix4 operator*(Matrix4 const& a, Matrix4 const& b)
{
	Matrix4 mResult;

	mResult.m_data[0]  = a.m_data[0] * b.m_data[0]  + a.m_data[1]  * b.m_data[4] + a.m_data[2]  * b.m_data[8]  + a.m_data[3]  * b.m_data[12];
	mResult.m_data[1]  = a.m_data[0] * b.m_data[1]  + a.m_data[1]  * b.m_data[5] + a.m_data[2]  * b.m_data[9]  + a.m_data[3]  * b.m_data[13];
	mResult.m_data[2]  = a.m_data[0] * b.m_data[2]  + a.m_data[1]  * b.m_data[6] + a.m_data[2]  * b.m_data[10] + a.m_data[3]  * b.m_data[14];
	mResult.m_data[3]  = a.m_data[0] * b.m_data[3]  + a.m_data[1]  * b.m_data[7] + a.m_data[2]  * b.m_data[11] + a.m_data[3]  * b.m_data[15];

	mResult.m_data[4]  = a.m_data[4] * b.m_data[0]  + a.m_data[5]  * b.m_data[4] + a.m_data[6]  * b.m_data[8]  + a.m_data[7]  * b.m_data[12];
	mResult.m_data[5]  = a.m_data[4] * b.m_data[1]  + a.m_data[5]  * b.m_data[5] + a.m_data[6]  * b.m_data[9]  + a.m_data[7]  * b.m_data[13];
	mResult.m_data[6]  = a.m_data[4] * b.m_data[2]  + a.m_data[5]  * b.m_data[6] + a.m_data[6]  * b.m_data[10] + a.m_data[7]  * b.m_data[14];
	mResult.m_data[7]  = a.m_data[4] * b.m_data[3]  + a.m_data[5]  * b.m_data[7] + a.m_data[6]  * b.m_data[11] + a.m_data[7]  * b.m_data[15];

	mResult.m_data[8]  = a.m_data[8] * b.m_data[0]  + a.m_data[9]  * b.m_data[4] + a.m_data[10] * b.m_data[8]  + a.m_data[11] * b.m_data[12];
	mResult.m_data[9]  = a.m_data[8] * b.m_data[1]  + a.m_data[9]  * b.m_data[5] + a.m_data[10] * b.m_data[9]  + a.m_data[11] * b.m_data[13];
	mResult.m_data[10] = a.m_data[8] * b.m_data[2]  + a.m_data[9]  * b.m_data[6] + a.m_data[10] * b.m_data[10] + a.m_data[11] * b.m_data[14];
	mResult.m_data[11] = a.m_data[8] * b.m_data[3]  + a.m_data[9]  * b.m_data[7] + a.m_data[10] * b.m_data[11] + a.m_data[11] * b.m_data[15];

	mResult.m_data[12] = a.m_data[12] * b.m_data[0] + a.m_data[13] * b.m_data[4] + a.m_data[14] * b.m_data[8]  + a.m_data[15] * b.m_data[12];
	mResult.m_data[13] = a.m_data[12] * b.m_data[1] + a.m_data[13] * b.m_data[5] + a.m_data[14] * b.m_data[9]  + a.m_data[15] * b.m_data[13];
	mResult.m_data[14] = a.m_data[12] * b.m_data[2] + a.m_data[13] * b.m_data[6] + a.m_data[14] * b.m_data[10] + a.m_data[15] * b.m_data[14];
	mResult.m_data[15] = a.m_data[12] * b.m_data[3] + a.m_data[13] * b.m_data[7] + a.m_data[14] * b.m_data[11] + a.m_data[15] * b.m_data[15];

	return mResult;
}

void
MatMult( const Matrix4& a, const Matrix4& b, Matrix4* _pResult )
{
	_pResult->m_data[0]  = a.m_data[0] * b.m_data[0]  + a.m_data[1] * b.m_data[4] + a.m_data[2]  * b.m_data[8] + a.m_data[3] * b.m_data[12];
	_pResult->m_data[1]  = a.m_data[0] * b.m_data[1]  + a.m_data[1] * b.m_data[5] + a.m_data[2]  * b.m_data[9] + a.m_data[3] * b.m_data[13];
	_pResult->m_data[2]  = a.m_data[0] * b.m_data[2]  + a.m_data[1] * b.m_data[6] + a.m_data[2]  * b.m_data[10] + a.m_data[3] * b.m_data[14];
	_pResult->m_data[3]  = a.m_data[0] * b.m_data[3]  + a.m_data[1] * b.m_data[7] + a.m_data[2]  * b.m_data[11] + a.m_data[3] * b.m_data[15];

	_pResult->m_data[4]  = a.m_data[4] * b.m_data[0]  + a.m_data[5] * b.m_data[4] + a.m_data[6]   * b.m_data[8] + a.m_data[7] * b.m_data[12];
	_pResult->m_data[5]  = a.m_data[4] * b.m_data[1]  + a.m_data[5] * b.m_data[5] + a.m_data[6]   * b.m_data[9] + a.m_data[7] * b.m_data[13];
	_pResult->m_data[6]  = a.m_data[4] * b.m_data[2]  + a.m_data[5] * b.m_data[6] + a.m_data[6]  * b.m_data[10] + a.m_data[7] * b.m_data[14];
	_pResult->m_data[7]  = a.m_data[4] * b.m_data[3]  + a.m_data[5] * b.m_data[7] + a.m_data[6]  * b.m_data[11] + a.m_data[7] * b.m_data[15];

	_pResult->m_data[8]  = a.m_data[8] * b.m_data[0]  + a.m_data[9] * b.m_data[4] + a.m_data[10]   * b.m_data[8] + a.m_data[11] * b.m_data[12];
	_pResult->m_data[9]  = a.m_data[8] * b.m_data[1]  + a.m_data[9] * b.m_data[5] + a.m_data[10]   * b.m_data[9] + a.m_data[11] * b.m_data[13];
	_pResult->m_data[10] = a.m_data[8] * b.m_data[2]  + a.m_data[9] * b.m_data[6] + a.m_data[10]  * b.m_data[10] + a.m_data[11] * b.m_data[14];
	_pResult->m_data[11] = a.m_data[8] * b.m_data[3]  + a.m_data[9] * b.m_data[7] + a.m_data[10]  * b.m_data[11] + a.m_data[11] * b.m_data[15];

	_pResult->m_data[12] = a.m_data[12] * b.m_data[0] + a.m_data[13] * b.m_data[4] + a.m_data[14]  * b.m_data[8] + a.m_data[15] * b.m_data[12];
	_pResult->m_data[13] = a.m_data[12] * b.m_data[1] + a.m_data[13] * b.m_data[5] + a.m_data[14]  * b.m_data[9] + a.m_data[15] * b.m_data[13];
	_pResult->m_data[14] = a.m_data[12] * b.m_data[2] + a.m_data[13] * b.m_data[6] + a.m_data[14] * b.m_data[10] + a.m_data[15] * b.m_data[14];
	_pResult->m_data[15] = a.m_data[12] * b.m_data[3] + a.m_data[13] * b.m_data[7] + a.m_data[14] * b.m_data[11] + a.m_data[15] * b.m_data[15];
}

const Vector3f operator*( const Matrix4& a, const Vector3f& b)
{
	Vector3f vResult;

	vResult.x = a.m_data[0] * b.x + a.m_data[1] * b.y + a.m_data[2]  * b.z;
	vResult.y = a.m_data[4] * b.x + a.m_data[5] * b.y + a.m_data[6]  * b.z;
	vResult.z = a.m_data[8] * b.x + a.m_data[9] * b.y + a.m_data[10] * b.z;
	
	return vResult;
};

const Vector3f operator*( const Vector3f& a, const Matrix4& b)
{
	Vector3f vResult;

	vResult.x = b.m_data[0] * a.x + b.m_data[4] * a.y + b.m_data[8]  * a.z;
	vResult.y = b.m_data[1] * a.x + b.m_data[5] * a.y + b.m_data[9]  * a.z;
	vResult.z = b.m_data[2] * a.x + b.m_data[6] * a.y + b.m_data[10] * a.z;
	
	return vResult;
};

const Vector4f operator*( const Matrix4& a, const Vector4f& b)
{
	Vector4f vResult;

	vResult.x = a.m_data[0] * b.x + a.m_data[3] * b.y + a.m_data[6] * b.z;
	vResult.z = a.m_data[1] * b.x + a.m_data[4] * b.y + a.m_data[7] * b.z;
	vResult.y = a.m_data[2] * b.x + a.m_data[5] * b.y + a.m_data[8] * b.z;
	
	return vResult;
};

const Vector4f operator*( const Vector4f& a, const Matrix4& b)
{
	Vector4f vResult;

	vResult.x = b.m_data[0] * a.x + b.m_data[3] * a.y + b.m_data[6] * a.z;
	vResult.y = b.m_data[1] * a.x + b.m_data[4] * a.y + b.m_data[7] * a.z;
	vResult.z = b.m_data[2] * a.x + b.m_data[5] * a.y + b.m_data[8] * a.z;
	
	return vResult;
};

Matrix4 operator/( Matrix4 const& a, const float _kfB )
{
	Matrix4 Result;

	Result.m_data[0]  = a.m_data[0]  / _kfB;
	Result.m_data[1]  = a.m_data[1]  / _kfB;
	Result.m_data[2]  = a.m_data[2]  / _kfB;
	Result.m_data[3]  = a.m_data[3]  / _kfB;
	Result.m_data[4]  = a.m_data[4]  / _kfB;
	Result.m_data[5]  = a.m_data[5]  / _kfB;
	Result.m_data[6]  = a.m_data[6]  / _kfB;
	Result.m_data[7]  = a.m_data[7]  / _kfB;
	Result.m_data[8]  = a.m_data[8]  / _kfB;
	Result.m_data[9]  = a.m_data[9]  / _kfB;
	Result.m_data[10] = a.m_data[10] / _kfB;
	Result.m_data[11] = a.m_data[11] / _kfB;
	Result.m_data[12] = a.m_data[12] / _kfB;
	Result.m_data[13] = a.m_data[13] / _kfB;
	Result.m_data[14] = a.m_data[14] / _kfB;
	Result.m_data[15] = a.m_data[15] / _kfB;

	return Result;
}

Matrix4 operator/( const float _kfB, Matrix4 const& a )
{
	Matrix4 Result;

	Result.m_data[0]  = a.m_data[0]  / _kfB;
	Result.m_data[1]  = a.m_data[1]  / _kfB;
	Result.m_data[2]  = a.m_data[2]  / _kfB;
	Result.m_data[3]  = a.m_data[3]  / _kfB;
	Result.m_data[4]  = a.m_data[4]  / _kfB;
	Result.m_data[5]  = a.m_data[5]  / _kfB;
	Result.m_data[6]  = a.m_data[6]  / _kfB;
	Result.m_data[7]  = a.m_data[7]  / _kfB;
	Result.m_data[8]  = a.m_data[8]  / _kfB;
	Result.m_data[9]  = a.m_data[9]  / _kfB;
	Result.m_data[10] = a.m_data[10] / _kfB;
	Result.m_data[11] = a.m_data[11] / _kfB;
	Result.m_data[12] = a.m_data[12] / _kfB;
	Result.m_data[13] = a.m_data[13] / _kfB;
	Result.m_data[14] = a.m_data[14] / _kfB;
	Result.m_data[15] = a.m_data[15] / _kfB;

  return Result;
}

}